const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

const getInjectCode = function (toInjectModules, options) {
    if (typeof toInjectModules === 'object') {
        toInjectModules = JSON.stringify(toInjectModules);
    }
    return `
    <script>
        var modules = ${toInjectModules}
        if (top !== self) {
            window.top.loadModules(modules);
        } else {
            modules.forEach(module => {
                insertScript(module)
            })
        }

        function insertScript(module, position) {
            var script = document.createElement('script');
            script.src = module.src;
            script.defer = true;
            document[position || 'head'].appendChild(script);
        }
    </script>`;
};

class IscWebpackPlugin {
    constructor(options) {
        this.name = 'IscWebpackPlugin';
        this.depVersionMap = {};
        this.options = {};
        this._init(options);
    }

    _init(options) {
        if (!options.packageInfo) {
            throw new Error('need pkgInfo');
        }

        Object.keys(options.packageInfo.dependencies).forEach((module) => {
            this.depVersionMap[module] = IscWebpackPlugin._getVersionInNodeModules(module);
        });

        let optionObj = {};
        optionObj.modules = {};
        if (!options.modules || !Array.isArray(options.modules) || !options.modules.length) {
            optionObj.modules = Object.keys(this.depVersionMap).map((dep) => ({
                name: dep,
                version: this.depVersionMap[dep]
            }));
        } else {
            optionObj.modules = options.modules.map((item) => {
                let depName;
                if (typeof item === 'object') {
                    depName = item.name;
                } else if (typeof item === 'string') {
                    depName = item;
                } else {
                    throw new Error('非法的 modules 入参');
                }
                return {
                    name: depName,
                    version: this.depVersionMap[depName]
                };
            });
        }
        this.options = optionObj;
    }

    apply(compiler) {
        // 把依赖的文件单独打包
        this._splitChunks(compiler);
        compiler.hooks.compilation.tap(this.name, (compilation) => {
            IscWebpackPlugin._getHtmlHook(
                compilation,
                'beforeAssetTagGeneration',
                'htmlWebpackPluginBeforeHtmlGeneration'
            ).tapAsync(this.name, (data, cb) => {
                data.assets.js = this._filterJs(data.assets.js);
                cb(null, data);
            });

            IscWebpackPlugin._getHtmlHook(compilation, 'beforeEmit', 'htmlWebpackPluginAfterHtmlProcessing').tapAsync(
                this.name,
                (data, cb) => {
                    let stringToInject = getInjectCode(this.options.modules, compiler.options);
                    const splitString = data.html.split('<head>');
                    splitString.splice(1, 0, '<head>' + stringToInject);
                    data.html = splitString.join('');
                    cb(null, data);
                }
            );
        });
    }
    _splitChunks(compiler) {
        compiler.options.optimization.runtimeChunk = 'single';
        const splitChunks = compiler.options.optimization.splitChunks || {};
        splitChunks.automaticNameDelimiter = '.';
        splitChunks.chunks = 'all';
        this.options.modules.forEach((item) => {
            // 添加 splitChunk
            splitChunks.cacheGroups[item.name] = {
                test: new RegExp('[\\/]node_modules[\\/]' + item.name + '[\\/]'),
                name: item.name + '@' + this.depVersionMap[item.name],
                priority: 10
            };
            // 设置 modules 的 src
            item.src = this._getScriptSrc(compiler, item.name, this.depVersionMap[item.name]) + '.js';
        });
        // splitChunks.cacheGroups.vendors = {
        //     test: /[\\/]node_modules[\\/]/,
        //     name: 'vendor',
        //     priority: 5
        // };
    }

    _getScriptSrc(compiler, depName, depVersion) {
        // js/
        let fileName = compiler.options.output.filename;
        if (fileName && fileName.startsWith('/')) {
            fileName = fileName.substring(1);
        }
        let splitedFileNames = fileName.split('/');
        let fileNameStart;
        if (splitedFileNames && Array.isArray(splitedFileNames) && splitedFileNames.length >= 2) {
            splitedFileNames.pop();
            fileNameStart = splitedFileNames.join('/');
        } else {
            fileNameStart = '';
        }
        let ret = compiler.options.output.publicPath || '';
        if (ret && !ret.endsWith('/')) ret += '/';
        ret += fileNameStart ? fileNameStart + '/' : '';
        ret += depName + '@' + depVersion;
        ret += '.js';
        return ret;
    }

    _filterJs(assets) {
        const toRemoveModules = this.options.modules.map((item) => `${item.name}@${item.version}`);
        assets = assets.filter((asset) => {
            return !toRemoveModules.some((toRemoveModule) => {
                return decodeURIComponent(asset).includes(toRemoveModule);
            });
        });
        return assets;
    }

    static _getVersionInNodeModules(name, pathToNodeModules = process.cwd()) {
        try {
            const pkgAddr = path.join(pathToNodeModules, 'node_modules', name, 'package.json');
            return require(pkgAddr).version;
        } catch (e) {
            return null;
        }
    }

    static _getHtmlHook(compilation, v4Name, v3Name) {
        try {
            /* istanbul ignore next */
            return HtmlWebpackPlugin.getHooks(compilation)[v4Name] || compilation.hooks[v3Name];
        } catch (e) {
            /* istanbul ignore next */
            return compilation.hooks[v3Name];
        }
    }
}

module.exports = IscWebpackPlugin;
